package pers.w.chaptertalk.reader;

import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;

import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import pers.w.chaptertalk.funs.Fun;
import pers.w.chaptertalk.funs.ISetting;
import pers.w.chaptertalk.tts.MainHandlerConstant;
import pers.w.chaptertalk.tts.control.InitConfig;
import pers.w.chaptertalk.tts.control.MySyntherizer;
import pers.w.chaptertalk.tts.control.NonBlockSyntherizer;
import pers.w.chaptertalk.tts.listener.UiMessageListener;
import pers.w.chaptertalk.tts.util.Auth;
import pers.w.chaptertalk.tts.util.AutoCheck;
import pers.w.chaptertalk.tts.util.OfflineResource;

/**
 * 合成demo。
 * 离在线合成SDK含在线和离线模型，纯离线合成SDK额外含有纯离线功能。
 * <p>
 * <p>
 * TtsMode.MIX 离在线合成根据网络状况优先走在线，在线时访问服务器失败后转为离线。
 * TtsMode.ONLINE 一直在线合成
 * TtsMode.OFFLINE 纯离线合成，一直离线。需要纯离线SDK
 */
public class Synth implements MainHandlerConstant, ISetting {

    // ================== 完整版初始化参数设置开始 ==========================
    /**
     * 发布时请替换成自己申请的appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。
     * 本demo的包名是com.baidu.tts.sample，定义在build.gradle中。
     */
    private String appId;

    private String appKey;

    private String secretKey;

    private String sn; // 纯离线合成SDK授权码；离在线合成SDK免费，没有此参数

    // TtsMode.MIX; 离在线融合，在线优先； TtsMode.ONLINE 纯在线； TtsMode.OFFLINE 纯离线合成，需要纯离线SDK
    private TtsMode ttsMode = TtsMode.MIX;

    // 离线发音选择，VOICE_FEMALE即为离线女声发音。
    // assets目录下bd_etts_common_speech_m15_mand_eng_high_am-mix_vXXXXXXX.dat为离线男声模型文件；
    // assets目录下bd_etts_common_speech_f7_mand_eng_high_am-mix_vXXXXX.dat为离线女声模型文件;
    // assets目录下bd_etts_common_speech_yyjw_mand_eng_high_am-mix_vXXXXX.dat 为度逍遥模型文件;
    // assets目录下bd_etts_common_speech_as_mand_eng_high_am_vXXXX.dat 为度丫丫模型文件;
    private String offlineVoice = OfflineResource.VOICE_MALE;

    // ===============初始化参数设置完毕，更多合成参数请至getParams()方法中设置 =================

    // 主控制类，所有合成控制方法从这个类开始
    private MySyntherizer synthesizer;


    private static final String TAG = "Synth";

    //private Map<String, String> map;

    private String[] offlineVoices = new String[]{
            OfflineResource.VOICE_FEMALE,
            OfflineResource.VOICE_MALE,
            OfflineResource.VOICE_DUXY,
            OfflineResource.VOICE_DUYY};
    private String online_voice = "0134";
    private Context context;
    private Handler mainHandler, checkHandler;


    Synth(Context context, Handler mainHandler, Handler checkHandler) {
        //  Auth.getInstance(context);
        this.context = context;
        this.mainHandler = mainHandler;
        this.checkHandler = checkHandler;
        load_setting();

        appId = Auth.getInstance(context).getAppId();
        appKey = Auth.getInstance(context).getAppKey();
        secretKey = Auth.getInstance(context).getSecretKey();
        sn = Auth.getInstance(context).getSn(); // 纯离线合成必须有此参数；离在线合成SDK免费，没有此参数

        initialTts(); // 初始化TTS引擎
        // map = new LinkedHashMap<>(4);

        //    loadModel(Integer.parseInt(speaker));

    }

    /**
     * 切换离线资源
     */
    private void loadModel(int model) {
        if (Fun.isDebug()) Log.d(TAG, "离线模型: " + model + " " + offlineVoices[model]);
        loadModel(offlineVoices[model]);
    }
//    void loadModel() {
//        loadModel(online_voice.indexOf(online_speaker));
//    }

    /**
     * 切换离线发音。注意需要添加额外的判断：引擎在合成时该方法不能调用
     */
    private void loadModel(String mode) {
        offlineVoice = mode;
        OfflineResource offlineResource = createOfflineResource(offlineVoice);
        if (Fun.isDebug()) Log.d(TAG, "切换离线语音：" + offlineResource.getModelFilename());
        int result = synthesizer.loadModel(offlineResource.getModelFilename(), offlineResource.getTextFilename());
        checkResult(result, "loadModel");
    }

    /**
     * 初始化引擎，需要的参数均在InitConfig类里
     * <p>
     * DEMO中提供了3个SpeechSynthesizerListener的实现
     * MessageListener 仅仅用log.i记录日志，在logcat中可以看见
     * UiMessageListener 在MessageListener的基础上，对handler发送消息，实现UI的文字更新
     * FileSaveListener 在UiMessageListener的基础上，使用 onSynthesizeDataArrived回调，获取音频流
     */
    private void initialTts() {
        LoggerProxy.printable(Fun.isDebug()); // 日志打印在logcat中
        // 设置初始化参数
        // 此处可以改为 含有您业务逻辑的SpeechSynthesizerListener的实现类
        SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);
        InitConfig config = getInitConfig(listener);
        synthesizer = new NonBlockSyntherizer(context, config, mainHandler); // 此处可以改为MySyntherizer 了解调用过程
    }


    /**
     * 合成的参数，可以初始化时填写，也可以在合成前设置。
     *
     * @return 合成参数Map
     */
    private Map<String, String> getParams() {
        Map<String, String> params = new HashMap<>();
        // 以下参数均为选填
        // 设置在线发声音人： 0 普通女声（默认） 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>, 其它发音人见文档
        params.put(SpeechSynthesizer.PARAM_SPEAKER, online_speaker);
        // 设置合成的音量，0-15 ，默认 5
        params.put(SpeechSynthesizer.PARAM_VOLUME, "12");
        // 设置合成的语速，0-15 ，默认 5
        params.put(SpeechSynthesizer.PARAM_SPEED, speed);
        // 设置合成的语调，0-15 ，默认 5
        params.put(SpeechSynthesizer.PARAM_PITCH, "5");

        params.put(SpeechSynthesizer.PARAM_MIX_MODE,
                useMobileNet?SpeechSynthesizer.MIX_MODE_HIGH_SPEED_NETWORK: SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI);
        // 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
        // MIX_MODE_DEFAULT 默认 ，wifi状态下使用在线，非wifi离线。在线状态下，请求超时6s自动转离线
        // MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线，非wifi离线。在线状态下， 请求超时1.2s自动转离线
        // MIX_MODE_HIGH_SPEED_NETWORK ， 3G 4G wifi状态下使用在线，其它状态离线。在线状态下，请求超时1.2s自动转离线
        // MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线，其它状态离线。在线状态下，请求超时1.2s自动转离线

        // params.put(SpeechSynthesizer.PARAM_MIX_MODE_TIMEOUT, SpeechSynthesizer.PARAM_MIX_TIMEOUT_TWO_SECOND);
        // 离在线模式，强制在线优先。在线请求后超时2秒后，转为离线合成。

        // 离线资源文件， 从assets目录中复制到临时目录，需要在initTTs方法前完成
        OfflineResource offlineResource = createOfflineResource(offlineVoice);
        // 声学模型文件路径 (离线引擎使用), 请确认下面两个文件存在
        params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, offlineResource.getTextFilename());
        params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE,
                offlineResource.getModelFilename());
        return params;
    }

    private InitConfig getInitConfig(SpeechSynthesizerListener listener) {
        Map<String, String> params = getParams();
        // 添加你自己的参数
        InitConfig initConfig;
        // appId appKey secretKey 网站上您申请的应用获取。注意使用离线合成功能的话，需要应用中填写您app的包名。包名在build.gradle中获取。
        if (sn == null) {
            initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
        } else {
            initConfig = new InitConfig(appId, appKey, secretKey, sn, ttsMode, params, listener);
        }
        // 如果您集成中出错，请将下面一段代码放在和demo中相同的位置，并复制InitConfig 和 AutoCheck到您的项目中
        // 上线时请删除AutoCheck的调用
        AutoCheck.getInstance(context.getApplicationContext()).check(initConfig, checkHandler);
        return initConfig;
    }


    private OfflineResource createOfflineResource(String voiceType) {
        OfflineResource offlineResource = null;
        try {
            offlineResource = new OfflineResource(context, voiceType);
        } catch (IOException e) {
            // IO 错误自行处理

            if (Fun.isDebug()) {
                e.printStackTrace();
                Log.d(TAG, "【error】:copy files from assets failed." + e.getMessage());
            }
        }
        return offlineResource;
    }

    /**
     * speak 实际上是调用 synthesize后，获取音频流，然后播放。
     * 获取音频流的方式见SaveFileActivity及FileSaveListener
     * 需要合成的文本text的长度不能超过1024个GBK字节。
     */
    void speak(String text) {


        // 需要合成的文本text的长度不能超过1024个GBK字节。

        // 合成前可以修改参数：
        // Map<String, String> params = getParams();
        // params.put(SpeechSynthesizer.PARAM_SPEAKER, "3"); // 设置为度逍遥
        // synthesizer.setParams(params);
        int result = synthesizer.speak(text);
        checkResult(result, "speak");
    }


    /**
     * 合成但是不播放，
     * 音频流保存为文件的方法可以参见SaveFileActivity及FileSaveListener
     */
    public void synthesize(String text) {
        int result = synthesizer.synthesize(text);
        checkResult(result, "synthesize");
    }

    /**
     * 批量播放
     */
    public void batchSpeak(List<Pair<String, String>> texts) {

//        List<Pair<String, String>> texts = new ArrayList<>();
//        texts.add(new Pair<>("开始批量播放，", "a0"));
//        texts.add(new Pair<>("123456，", "a1"));
//        texts.add(new Pair<>("欢迎使用百度语音，，，", "a2"));
//        texts.add(new Pair<>("重(chong2)量这个是多音字示例", "a3"));
        int result = synthesizer.batchSpeak(texts);
        checkResult(result, "batchSpeak");
    }


    private void checkResult(int result, String method) {
        if (result != 0) {
            Log.d(TAG, "error code :" + result + " method:" + method);
        }
    }


    /**
     * 暂停播放。仅调用speak后生效
     */
    void pause() {
        int result = synthesizer.pause();
        checkResult(result, "pause");
    }

    /**
     * 继续播放。仅调用speak后生效，调用pause生效
     */
    void resume() {
        int result = synthesizer.resume();
        checkResult(result, "resume");
    }

    /*
     * 停止合成引擎。即停止播放，合成，清空内部合成队列。
     */
    void stop() {
        int result = synthesizer.stop();
        checkResult(result, "stop");
    }

    void release() {
        Log.d(TAG, "release:");
        if (null != synthesizer) {
            synthesizer.release();
            synthesizer = null;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        release();
        super.finalize();
    }

    int getSpeed() {
        return Integer.parseInt(speed);
    }

    private String speed = "5";

    void setSpeed(int speed) {
        String s = String.valueOf(speed);

        if (!s.equals(this.speed)) {
            this.speed = s;
            Map<String, String> params = new HashMap<>();// getParams();
            params.put(SpeechSynthesizer.PARAM_SPEED, this.speed); // 设置
            synthesizer.setParams(params);
            save_setting();
        }
    }

    int getSpeaker() {
        return online_voice.indexOf(online_speaker) + 1;
    }

    private String online_speaker;//这里在set get的时候需要转换，并在set时候修改offline

    void setSpeaker(int speaker) {
        String s = online_voice.substring(speaker - 1, speaker);
        if (!s.equals(this.online_speaker)) {
            this.online_speaker = s;
            Map<String, String> params = new HashMap<>();// getParams();
            params.put(SpeechSynthesizer.PARAM_SPEAKER, this.online_speaker); // 设置
            synthesizer.setParams(params);
            loadModel(speaker - 1);
            save_setting();
        }
    }
    @Override
    public void load_setting() {
        Properties properties = new Properties();
        try {
            InputStream is = context.openFileInput(CONFIG_NAME);
            properties.load(is);
            online_speaker = properties.getProperty(SPEAKER, "0");//实际1会在setSpeak内部-1，从0开始
            speed = properties.getProperty(SPEED, "5");
            offlineVoice = offlineVoices[online_voice.indexOf(online_speaker)];//离线声音初始化

            useMobileNet= Boolean.parseBoolean( properties.getProperty(USE_MOBILE_NET, "FALSE"));
            is.close();
        } catch (IOException e) {
            // e.printStackTrace();
            //设置默认值，如果没用设置，不至于出错
            online_speaker = "0";
            speed = "5";
            offlineVoice = offlineVoices[0];
            useMobileNet= false;
        }

    }

    private final static String
            CONFIG_NAME = "spoken_config", SPEAKER = "speaker", SPEED = "speed",USE_MOBILE_NET="use_mobile_net";
    @Override
    public  void save_setting() {
        Properties properties = new Properties();
        try {
            OutputStream out = context.openFileOutput(CONFIG_NAME, Context.MODE_PRIVATE);

            properties.setProperty(SPEAKER, online_speaker);
            properties.setProperty(SPEED, speed);
            properties.setProperty(USE_MOBILE_NET, String.valueOf(useMobileNet));

            properties.store(out, null);
            out.close();
            if (Fun.isDebug()) Log.d("config", "save_setting: true");
        } catch (IOException e) {
            //e.printStackTrace();
        }
    }

    public boolean isUseMobileNet() {
        return useMobileNet;
    }

    public void setUseMobileNet(boolean useMobileNet) {
        this.useMobileNet = useMobileNet;
        Map<String, String> params = new HashMap<>();// getParams();
        params.put(SpeechSynthesizer.PARAM_MIX_MODE,
                useMobileNet?SpeechSynthesizer.MIX_MODE_HIGH_SPEED_NETWORK: SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI);
        synthesizer.setParams(params);
    }

    private boolean useMobileNet;
}
